package cn.org.atool.fluent.mybatis.metadata;

import cn.org.atool.fluent.mybatis.metadata.feature.DbFeature;
import cn.org.atool.fluent.mybatis.metadata.feature.EscapeExpress;
import cn.org.atool.fluent.mybatis.metadata.feature.PagedFormat;
import lombok.Getter;

import static cn.org.atool.fluent.mybatis.metadata.DbFeatures.*;
import static cn.org.atool.fluent.mybatis.metadata.feature.PagedFormat.MYSQL_LIMIT;

/**
 * DbType 数据库类型
 *
 * @author darui.wu Created by darui.wu on 2020/6/16.
 * @see EscapeExpress
 */
@SuppressWarnings("unused")
public enum DbType {
    /**
     * MYSQL
     */
    MYSQL(DF_MYSQL),
    /**
     * MARIADB
     */
    MARIADB(DF_MARIADB),
    /**
     * H2
     */
    H2(DF_H2),
    /**
     * SQLITE
     * https://www.sqlite.org/lang_keywords.html
     */
    SQLITE(DF_SQLITE),
    /**
     * Oracle 11g and less
     */
    ORACLE(DF_ORACLE),
    /**
     * Oracle 12c
     */
    ORACLE12(DF_ORACLE12),
    /**
     * DB2
     */
    DB2(DF_DB2),
    /**
     * HSQL
     */
    HSQL(DF_HSQLDB),
    /**
     * POSTGRE
     */
    POSTGRE_SQL(DF_POSTGRE_SQL),
    /**
     * derby
     */
    DERBY(DF_DERBY),
    /**
     * click house
     */
    CLICK_HOUSE(DF_CLICK_HOUSE),
    /**
     * informix
     */
    INFORMIX(DF_INFORMIX),
    /**
     * SQLSERVER2005
     */
    SQL_SERVER2005(DF_SQL_SERVER2005),
    /**
     * SQLSERVER
     */
    SQL_SERVER2012(DF_SQL_SERVER2012),
    /**
     * Firebird
     */
    FIREBIRD(DF_FIREBIRD),
    /**
     * 其它数据库1, 按标准语法进行处理
     * 这里定义2个OTHER, 是为了尽可能满足一个应用使用到多数据源类型的场景
     */
    OTHER("other", MYSQL_LIMIT),
    /**
     * 其它数据库2, 按标准语法进行处理
     */
    OTHER_2("other2", MYSQL_LIMIT);

    @Getter
    public final DbFeature feature;

    DbType(String alias, PagedFormat paged) {
        this.feature = new DbFeature(alias, paged);
    }

    DbType(DbFeature feature) {
        this.feature = feature;
    }

    /**
     * 数据库字段用反义符包起来
     *
     * @param column 数据库字段
     * @return 反义符包裹后的数据库字段
     */
    public String wrap(String column) {
        return this.feature.getEscape().wrap(column);
    }

    /**
     * 去掉转义符
     *
     * @param column 可能带转义符的字段名称
     * @return 去掉转义符后的名称
     */
    public String unwrap(String column) {
        return this.feature.getEscape().unwrap(column);
    }

    /**
     * 根据数据库类型组装分页语句
     *
     * @param query          未分页的查询语句
     * @param pagedOffset    分页偏移开始变量
     * @param pagedSize      分页大小变量
     * @param pagedEndOffset 分页偏移结束变量
     * @return 分页查询语句
     */
    public String paged(String query, String pagedOffset, String pagedSize, String pagedEndOffset) {
        return this.feature.getPaged().build(query.trim(), pagedOffset, pagedSize, pagedEndOffset);
    }

    /**
     * 设置数据库字段的反义处理
     *
     * @param expression 反义处理函数, 比如 mysql: `?`, sqlserver: [?], 或 无反义处理: ?
     */
    public void setEscapeExpress(String expression) {
        this.feature.setEscape(new EscapeExpress(expression));
    }

    /**
     * 设置数据库分页处理语法规则
     *
     * @param pagedFormat {@link PagedFormat}
     */
    public void setPagedFormat(String pagedFormat) {
        this.feature.setPaged(new PagedFormat(pagedFormat));
    }

    /**
     * 根据配置查找对应的数据库类型<br>
     *
     * @param type 数据库类型
     * @return 数据库类型
     */
    public static DbType dbType(final String type) {
        for (DbType dbType : DbType.values()) {
            if (dbType.name().equalsIgnoreCase(type)) {
                return dbType;
            }
        }
        return null;
    }

    /**
     * 判断数据库类型
     *
     * @param driverName 驱动器类
     * @param url        数据库链接
     * @return 类型枚举值
     */
    public static DbType getDbType(String driverName, String url) {
        DbType dbType = isDbType(driverName);
        if (dbType == null) {
            dbType = isDbType(url);
        }
        return dbType == null ? DbType.MYSQL : dbType;
    }

    private static DbType isDbType(String url) {
        if (url == null || url.trim().isEmpty()) {
            return null;
        }
        String temp = url.toLowerCase();
        DbType[] dbTypes = DbType.values();
        for (DbType type : dbTypes) {
            if (temp.contains(type.feature.getName().toLowerCase())) {
                return type;
            }
        }
        return null;
    }
}